#include <Kharlia/Core/Common.h>
#include <Kharlia/Core/Logger.h>
#include <Kharlia/Core/ObjectUtil.h>
#include <Kharlia/Core/ObjectBase.h>
#include <Kharlia/Core/Subsystem.h>
#include <Kharlia/Core/Tasker.h>
#include <Kharlia/Core/App.h>
#include <Kharlia/Core/Converters.h>
#include <Kharlia/Core/IO.h>
//#include <Kharlia/Core/Modules/slsocketmodule.h>
#include <iostream>
#include <vector>

#include <Kharlia/Core/Debug.h>

#define RELATIVE_DLL_DIRECTORY_W L"bin"

namespace kharlia { namespace core {

namespace {

object GInnerModule;
object GModule;
shared_ptr<FLogger> GLogger;

} // namespace

int CallBaseTraverse(PyObject* self, visitproc visit, void* arg,
                     objects::instance<>* inst, traverseproc current) {
    PyTypeObject* base = Py_TYPE(self);
    traverseproc basefunc = base->tp_traverse;
    // Skip anything before current
    while (basefunc != current) {
        base = base->tp_base;
        assert(base);
        basefunc = base->tp_traverse;
    }
    // Skip current and any subtype_traverse that might be below us
    while (basefunc == current || basefunc == Py_TYPE(self)->tp_traverse) {
        base = base->tp_base;
        assert(base);
        basefunc = base->tp_traverse;
    }
    // Call the super traverse if we find one
    if (basefunc)
        return basefunc(self, visit, arg);
    // If not, visit the instance dict if one exists for Boost.Python.instance
    else if (base == objects::class_type().get() && inst->dict)
        Py_VISIT(inst->dict);
    return 0;
}

int CallBaseClear(PyObject* self, objects::instance<>* inst, inquiry current) {
    PyTypeObject* base = Py_TYPE(self);
    inquiry basefunc = base->tp_clear;
    while (basefunc != current) {
        base = base->tp_base;
        assert(base);
        basefunc = base->tp_clear;
    }
    while (basefunc == current || basefunc == Py_TYPE(self)->tp_clear) {
        base = base->tp_base;
        assert(base);
        basefunc = base->tp_clear;
    }
    if (basefunc)
        return basefunc(self);
    else if (base == objects::class_type().get() && inst->dict)
        Py_CLEAR(inst->dict);
    return 0;
}

#define AS_GC(o) ((PyGC_Head *)(o)-1)

// This function exists because several shared_ptr instances can
// share the same python reference thanks to Boost.Python
// Visiting each shared_ptr normally assumes each has its own reference,
// which causes counting errors with the GC.
//
// This function detects when a count is being done, and ensures that
// a particular shared_ptr deleter is only counted once during a cycle.
// To do this, a new field had to be added to Boost.Python's
// shared_ptr_deleter, the gc field.
int VisitSharedPtrDeleter(converter::shared_ptr_deleter* deleter,
                          visitproc visit, void* arg) {
    assert(deleter);
    PyObject* op = deleter->owner.get();
    if (op && PyObject_IS_GC(op)) {
        PyGC_Head *gc = AS_GC(op);
        bool isdecref = false;
        Py_ssize_t refs = gc->gc.gc_refs;

        // Since Python doesn't make its visit functions public,
        // we need to detect the right one here
        if (refs >= 0 && arg == NULL) {
            isdecref = true;
            if (deleter->gc != 0)
                refs = ++gc->gc.gc_refs;
        }

        assert(!isdecref || refs > 0);
        Py_VISIT(op);

        if (isdecref) {
            // Make sure our previous detection was accurate
            assert(gc->gc.gc_refs == refs - 1);
            // Note how many counts we've done
            deleter->gc++;
        }
        else {
            // The garbage collector counts all refs at once, so in this
            // case, we're done and can clear the flag for the next
            // collection.
            deleter->gc = 0;
        }

    }
    return 0;
}

object GetInnerModuleAttr(str name) {
    assert(!GInnerModule.is_none());
    return GInnerModule.attr(name);
}

object GetModuleAttr(str name) {
    assert(!GModule.is_none());
    return GModule.attr(name);
}

void SetModuleAttr(str name, object value) {
    assert(!GModule.is_none());
    GModule.attr(name) = value;
}

const shared_ptr<FLogger>& GetMainLogger() {
    return GLogger;
}

void _SetMainLogger(const shared_ptr<FLogger>& logger) {
    GLogger = logger;
}

void TestExtract(object obj) {
    //RegisterValueToSharedPtr<CObject>();
    //RegisterValueToSharedPtr<CApp>();
    //shared_ptr<CObject> ex2 = extract<shared_ptr<CObject>>(obj);
    //shared_ptr<CObject> ex = ExtractHeldSharedPtr<CObject>(obj);
    //shared_ptr<CApp> ex3 = extract<shared_ptr<CApp>>(obj);
    //bool m = IsManagingPyObject(ex);
    //bool m2 = IsManagingPyObject(ex2);
    //bool m3 = IsManagingPyObject(ex3);
    //std::cout << "DONE: " << m << std::endl;
}

object GetBoostPythonClassType() {
    return object(objects::class_metatype());
}
object GetBoostPythonInstanceType() {
    return object(objects::class_type());
}
str TestFileIO(str filename) {
    std::string fn = extract<const char*>(filename);
    intrusive_ptr<FFileLoadRequest> req(new FFileLoadRequest(fn, 0, -1));
    req->ExecuteStackless();
    return str(req->GetResult());
}
void TestFileIODump(str filename, str dump) {
    intrusive_ptr<FFileDumpRequest> req(
        new FFileDumpRequest(extract<std::string>(filename),
                                extract<std::string>(dump)));
    req->ExecuteStackless();
}
void TestFileIODumpCB(str filename, str dump, object cb) {
    intrusive_ptr<FFileDumpRequest> req(
        new FFileDumpRequest(extract<std::string>(filename),
                                extract<std::string>(dump)));
    req->ExecuteAsync(cb);
}
void CallbackFunc() {
    GetMainLogger()->Debug("NATIVE CALLBACK CALLED!");
}
void TestFileIODumpCBC(str filename, str dump) {
    intrusive_ptr<FFileDumpRequest> req(
        new FFileDumpRequest(extract<std::string>(filename),
                                extract<std::string>(dump)));
    req->ExecuteAsync(&CallbackFunc);
}

void TestUnicode(unicode stro) {
    std::wstring cstro = extract<std::wstring>(stro);
    const wchar_t* cstroc = cstro.c_str();
    std::cout << cstroc << std::endl;
    unicode stro2 = stro;
    unicode stro3(cstroc);
    std::wstring cstro3 = extract<std::wstring>(stro3);
    const wchar_t* cstroc3 = cstro3.c_str();
    std::cout << cstroc3 << std::endl;

    std::size_t slen = len(stro);
    scoped_array<wchar_t> cstroc4(new wchar_t[slen + 1]); // +1 for null terminator
    std::size_t copied = stro.as_wchar(cstroc4.get(), slen + 1);
    assert(slen == copied);
    wchar_t end = cstroc4.get()[slen];
    wchar_t nullt = L'\0';
    std::cout << cstroc4.get() << std::endl;

    object strox(cstroc3);
    unicode stroxo = extract<unicode>(strox);

    str rstro = extract<str>(stro);
    const char* rstroc = PyString_AS_STRING(rstro.ptr());
    std::cout << rstroc << std::endl;

    unicode rstrou = extract<unicode>(rstro);
    std::wstring rstrouc = extract<std::wstring>(rstrou);
}

void TestWeakPtr(shared_ptr<CObject> op, shared_ptr<CApp> ap) {
    weak_ptr<CObject> wop = GetWeakPtr(op);
    weak_ptr<CApp> wap = GetWeakPtr(ap);
    shared_ptr<void> spv((void*)0, converter::shared_ptr_deleter(handle<>()));
    weak_ptr<FIORequest> wio = GetWeakPtr(
        shared_ptr<FIORequest>(spv, (FIORequest*)0)
    );
}

void _ExitModule() {
    if (GLogger)
        GLogger->Debug("exiting core module");

    _FiniTaskerBindings();

    GConstructObjectFunc = object();
    GResolveNameFunc = object();
    GLogger.reset();
    GInnerModule = object();
    GModule = object();
}

void _InitModule() {
    using namespace py;

    object __core = import("kharlia.__core");
    object atexit = import("atexit");

    // Register module exit function
    atexit.attr("register")(make_function(&_ExitModule));
    
    GInnerModule = __core;
    GModule = scope();

    // Logger initialized in CApp::_Run()

    _InitExceptions();
    _InitConverters();

    _InitObjectBindings();
    _InitAppBindings(); // calls _InitTaskerBindings()
    _InitIOBindings();

    //scope().attr("_slsocket") = InitSLSocketModule("kharlia._core._slsocket");

    def("GetBoostPythonClassType", &GetBoostPythonClassType);
    def("GetBoostPythonInstanceType", &GetBoostPythonInstanceType);
    def("TestFileIO", &TestFileIO);
    def("TestFileIODump", &TestFileIODump);
    def("TestFileIODumpCB", &TestFileIODumpCB);
    def("TestFileIODumpCBC", &TestFileIODumpCBC);
    def("TestExtract", &TestExtract);
    def("TestUnicode", &TestUnicode);
    def("TestWeakPtr", &TestWeakPtr);
}

}} // namespace Kharlia

BOOST_PYTHON_MODULE(_core) {
    kharlia::core::_InitModule();
}